package com.spring.mvc.servlet.version_01;

import com.spring.mvc.annotation.IAutowired;
import com.spring.mvc.annotation.IController;
import com.spring.mvc.annotation.IRequestMapping;
import com.spring.mvc.annotation.IService;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @Author: WuPeng
 * @Date: 2021/6/5 10:46 上午
 */
public class IDispatcherServlet extends HttpServlet {

    private List<String> BEAN_CLASS_NAMES = new CopyOnWriteArrayList<>();

    // beanName --> bean instance
    private Map<String, Object> BEAN_MAPPING = new ConcurrentHashMap<>();

    // url --> method
    private Map<String, Object> ROUTE_MAPPING = new ConcurrentHashMap<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 exception " + Arrays.toString(e.getStackTrace()));
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath, "").replaceAll("/+", "/");
        if (!this.ROUTE_MAPPING.containsKey(url)) {
            resp.getWriter().write("404 Not Found!");
            return;
        }
        Method method = (Method) this.ROUTE_MAPPING.get(url);
        Map<String, String[]> params = req.getParameterMap();
        method.invoke(this.BEAN_MAPPING.get(method.getDeclaringClass().getName()),
                new Object[]{params.get("name")[0], req, resp});
    }

    /**
     * init() 初始化方法，用于容器的初始化等
     *      初始化所有相关的类、IOC容器、servletBean
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        InputStream is = null;
        try {
            Properties properties = new Properties();
            is = this.getClass().getClassLoader().getResourceAsStream(config.getInitParameter("contextConfigLocation"));
            properties.load(is);

            String scanPackage = properties.getProperty("scanPackage");
            //扫描包路径下的类
            doScanner(scanPackage);
            //处理扫描到的类文件
            for (String className : BEAN_CLASS_NAMES){
                if (!className.contains(".")) continue;
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(IController.class)){
                    BEAN_MAPPING.put(className, clazz.newInstance());
                    String path = "";
                    if (clazz.isAnnotationPresent(IRequestMapping.class))
                        path = clazz.getAnnotation(IRequestMapping.class).value();

                    Method[] methods = clazz.getDeclaredMethods();
                    for (Method method : methods){
                        if (!method.isAnnotationPresent(IRequestMapping.class)) continue;
                        String url = ("/" + path + "/" + method.getAnnotation(IRequestMapping.class).value()).replaceAll("/+", "/");
                        ROUTE_MAPPING.put(url, method);
                        System.out.printf("Mapped %s , %s", url, method );
                    }
                }
                else if (clazz.isAnnotationPresent(IService.class)){
                    String beanName = clazz.getAnnotation(IService.class).value();
                    beanName = "".equals(beanName) ? clazz.getName() : beanName;
                    Object instance = clazz.newInstance();
                    BEAN_MAPPING.put(beanName, instance);
                    for(Class cla : clazz.getInterfaces()){
                        BEAN_MAPPING.put(cla.getName(), instance);
                    }
                }
                else {
                    continue;
                }
            }
            // 处理Controller类
            for (Object method : BEAN_MAPPING.values()){
                if (method == null) continue;
                Class<?> clazz = method.getClass();
                if (clazz.isAnnotationPresent(IController.class)){
                    Field[] fields = clazz.getDeclaredFields();
                    for (Field field : fields){
                        if (!field.isAnnotationPresent(IAutowired.class)) continue;
                        IAutowired iAutowired = field.getAnnotation(IAutowired.class);
                        String beanName = iAutowired.value();
                        beanName = "".equals(beanName) ? field.getType().getName() : beanName;
                        field.setAccessible(true);
                        try {
                            field.set(BEAN_MAPPING.get(clazz.getName()), BEAN_MAPPING.get(beanName));
                        } catch (IllegalAccessException e){
                            e.printStackTrace();
                        }
                    }
                }
            }
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if (is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println("MVC has been init");
    }

    private void doScanner(String scanPackage) {
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File classDir = new File(url.getFile());
        for (File file : classDir.listFiles()){
            if (file.isDirectory()){
                doScanner(scanPackage + "." + file.getName());
            }
            else {
                if (!file.getName().endsWith(".class")) continue;
                String clazzName = (scanPackage + "." + file.getName().replace(".class", ""));
                BEAN_CLASS_NAMES.add(clazzName);
            }
        }
    }
}
